﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using suanpan.API.Components.Core;
using suanpan.API.Components.Behaviors.ErrorHandling;

namespace suanpan.API.Components.Channels.Core
{
    class RestChessChannel : ChannelBase, IReplyChannel
    {
        #region Properties

        protected IReplyChannel InnerChannel { get; set; }

        #endregion

        #region CTor


        public RestChessChannel(ChannelManagerBase manager, IReplyChannel innerChannel)
            : base(manager)
        {
            if (innerChannel == null)
            {
                throw new ArgumentException("RestChessChannel requires a non-null inner channel.", "innerChannel");
            }
            this.InnerChannel = innerChannel;
        }

        #endregion

        #region Methods

        virtual protected void OnReceive(ref Message message) { }

        virtual protected void OnSend(ref Message message) { }

        public bool ProcessRequestContext(ref RequestContext requestContext)
        {
            if (requestContext == null)
            {
                return true;
            }

            Message m = requestContext.RequestMessage;
            Message originalMessage = m;

            this.OnReceive(ref m);
            if (m != null || originalMessage == null)
            {
                requestContext = new InterceptingRequestContext(this, requestContext);
                bool shortCircuit = false;
                //Here is where we check if we need to short circuit the request and send back an error Message.
                if (m.Properties.ContainsKey("SHORTCIRCUITMESSAGE"))
                    shortCircuit = Convert.ToBoolean(m.Properties["SHORTCIRCUITMESSAGE"]);
                if (shortCircuit)
                {
                    //first we send back the errorMessage
                    requestContext.Reply(m);
                    //Cancel the original request
                    requestContext.Abort();
                    requestContext = null;
                }
            }
            else
            {
                requestContext.Abort();
                requestContext = null;
            }

            return requestContext != null;
        }

        protected void SendErrorMessage(ref Message incomingMessage, RestErrorMessage errorMessage)
        {
            if (errorMessage != null)
            {
                errorMessage.Properties.Add("SHORTCIRCUITMESSAGE", true);
                incomingMessage = errorMessage;
            }
        }
        #endregion

        #region ChannelBase Overrides

        public override T GetProperty<T>()
        {
            T baseProperty = base.GetProperty<T>();
            if (baseProperty != null)
            {
                return baseProperty;
            }

            return this.InnerChannel.GetProperty<T>();
        }
        protected override void OnAbort()
        {
            this.InnerChannel.Abort();
        }

        protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return this.InnerChannel.BeginClose(timeout, callback, state);
        }

        protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return this.InnerChannel.BeginOpen(timeout, callback, state);
        }

        protected override void OnClose(TimeSpan timeout)
        {
            this.InnerChannel.Close(timeout);
        }

        protected override void OnEndClose(IAsyncResult result)
        {
            this.InnerChannel.EndClose(result);
        }

        protected override void OnEndOpen(IAsyncResult result)
        {
            this.InnerChannel.EndOpen(result);
        }

        protected override void OnOpen(TimeSpan timeout)
        {
            this.InnerChannel.Open(timeout);
        }
        

        #endregion

        #region IReplyChannel Members

        public IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
        {
            ReceiveRequestAsyncResult result = new ReceiveRequestAsyncResult(this, timeout, callback, state);
            result.Begin();
            return result;
        }

        public IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state)
        {
            return BeginReceiveRequest(this.DefaultReceiveTimeout, callback, state);
        }

        public IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
        {
            TryReceiveRequestAsyncResult result = new TryReceiveRequestAsyncResult(this, timeout, callback, state);
            result.Begin();
            return result;
        }

        public IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return this.InnerChannel.BeginWaitForRequest(timeout, callback, state);
        }

        public RequestContext EndReceiveRequest(IAsyncResult result)
        {
            return ReceiveRequestAsyncResult.End(result);
        }

        public bool EndTryReceiveRequest(IAsyncResult result, out RequestContext context)
        {
            return TryReceiveRequestAsyncResult.End(result, out context);
        }

        public bool EndWaitForRequest(IAsyncResult result)
        {
            return this.InnerChannel.EndWaitForRequest(result);
        }

        public System.ServiceModel.EndpointAddress LocalAddress
        {
            get { return this.InnerChannel.LocalAddress; }
        }

        public RequestContext ReceiveRequest(TimeSpan timeout)
        {
            RequestContext requestContext;
            while (true)
            {
                requestContext = this.InnerChannel.ReceiveRequest(timeout);
                if (ProcessRequestContext(ref requestContext))
                {
                    break;
                }
            }

            return requestContext;
        }

        public RequestContext ReceiveRequest()
        {
            return ReceiveRequest(DefaultReceiveTimeout);
        }

        public bool TryReceiveRequest(TimeSpan timeout, out RequestContext context)
        {
            bool result;

            while (true)
            {
                result = this.InnerChannel.TryReceiveRequest(timeout, out context);
                if (!result || ProcessRequestContext(ref context))
                {
                    break;
                }
            }

            return result;
        }

        public bool WaitForRequest(TimeSpan timeout)
        {
            return this.InnerChannel.WaitForRequest(timeout);
        }

        #endregion

        #region Asynchronous Classes

        abstract class ReceiveRequestAsyncResultBase : AsyncResult
        {
            RequestContext requestContext;
            RestChessChannel channel;
            AsyncCallback onReceive;

            protected ReceiveRequestAsyncResultBase(RestChessChannel channel,
                AsyncCallback callback, object state)
                : base(callback, state)
            {
                this.channel = channel;
                this.onReceive = new AsyncCallback(OnReceive);
            }

            protected RequestContext RequestContext
            {
                get { return this.requestContext; }
            }

            public void Begin()
            {
                IAsyncResult result = BeginReceiveRequest(onReceive, null);
                if (result.CompletedSynchronously)
                {
                    if (HandleReceiveComplete(result))
                    {
                        base.Complete(true);
                    }
                }
            }

            protected abstract IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state);
            protected abstract RequestContext EndReceiveRequest(IAsyncResult result);

            bool HandleReceiveComplete(IAsyncResult result)
            {
                while (true)
                {
                    this.requestContext = EndReceiveRequest(result);
                    if (channel.ProcessRequestContext(ref requestContext))
                    {
                        return true;
                    }

                    // try again
                    result = BeginReceiveRequest(onReceive, null);
                    if (!result.CompletedSynchronously)
                    {
                        return false;
                    }
                }
            }

            void OnReceive(IAsyncResult result)
            {
                if (result.CompletedSynchronously)
                {
                    return;
                }

                bool completeSelf = false;
                Exception completeException = null;
                try
                {
                    completeSelf = HandleReceiveComplete(result);
                }
                catch (Exception e)
                {
                    completeException = e;
                    completeSelf = true;
                }

                if (completeSelf)
                {
                    base.Complete(false, completeException);
                }
            }
        }

        class TryReceiveRequestAsyncResult : ReceiveRequestAsyncResultBase
        {
            IReplyChannel innerChannel;
            TimeSpan timeout;
            bool returnValue;

            public TryReceiveRequestAsyncResult(RestChessChannel channel, TimeSpan timeout,
                AsyncCallback callback, object state)
                : base(channel, callback, state)
            {
                this.innerChannel = channel.InnerChannel;
                this.timeout = timeout;
            }

            protected override IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state)
            {
                return this.innerChannel.BeginTryReceiveRequest(this.timeout, callback, state);
            }

            protected override RequestContext EndReceiveRequest(IAsyncResult result)
            {
                RequestContext requestContext;
                this.returnValue = this.innerChannel.EndTryReceiveRequest(result, out requestContext);
                return requestContext;
            }

            public static bool End(IAsyncResult result, out RequestContext requestContext)
            {
                TryReceiveRequestAsyncResult thisPtr = AsyncResult.End<TryReceiveRequestAsyncResult>(result);
                requestContext = thisPtr.RequestContext;
                return thisPtr.returnValue;
            }
        }

        class ReceiveRequestAsyncResult : ReceiveRequestAsyncResultBase
        {
            IReplyChannel innerChannel;
            TimeSpan timeout;

            public ReceiveRequestAsyncResult(RestChessChannel channel, TimeSpan timeout, AsyncCallback callback, object state)
                : base(channel, callback, state)
            {
                this.innerChannel = channel.InnerChannel;
                this.timeout = timeout;
            }

            protected override IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state)
            {
                return this.innerChannel.BeginReceiveRequest(this.timeout, callback, state);
            }

            protected override RequestContext EndReceiveRequest(IAsyncResult result)
            {
                return this.innerChannel.EndReceiveRequest(result);
            }

            public static RequestContext End(IAsyncResult result)
            {
                ReceiveRequestAsyncResult thisPtr = AsyncResult.End<ReceiveRequestAsyncResult>(result);
                return thisPtr.RequestContext;
            }
        }


        class InterceptingRequestContext : RequestContext
        {
            RestChessChannel channel;
            RequestContext innerContext;

            public InterceptingRequestContext(RestChessChannel channel, RequestContext innerContext)
            {
                this.channel = channel;
                this.innerContext = innerContext;
            }

            public override Message RequestMessage
            {
                get
                {
                    return this.innerContext.RequestMessage;
                }
            }

            public override void Abort()
            {
                this.innerContext.Abort();
            }

            public override IAsyncResult BeginReply(Message message, AsyncCallback callback, object state)
            {
                return BeginReply(message, channel.DefaultSendTimeout, callback, state);
            }

            public override IAsyncResult BeginReply(Message message, TimeSpan timeout, AsyncCallback callback, object state)
            {
                Message m = message;
                this.OnSend(ref m);
                return this.innerContext.BeginReply(m, timeout, callback, state);
            }

            public override void Close()
            {
                this.innerContext.Close();
            }

            public override void Close(TimeSpan timeout)
            {
                this.innerContext.Close(timeout);
            }

            protected override void Dispose(bool disposing)
            {
                try
                {
                    if (disposing)
                        ((IDisposable)this.innerContext).Dispose();
                }
                finally
                {
                    base.Dispose(disposing);
                }
            }

            public override void EndReply(IAsyncResult result)
            {
                this.innerContext.EndReply(result);
            }

            void OnSend(ref Message message)
            {
                this.channel.OnSend(ref message);
            }

            public override void Reply(Message message)
            {
                Reply(message, channel.DefaultSendTimeout);
            }

            public override void Reply(Message message, TimeSpan timeout)
            {
                Message m = message;
                this.OnSend(ref m);
                this.innerContext.Reply(m, timeout);
            }
        }
        #endregion
    }
}
